﻿using Polly;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions;
using System.Net.Sockets;

namespace PLM.ConfigurationCenter.EventBus.RabbitMQ {
    /// <summary>
    /// 
    /// </summary>
    public class RabbitMQConnection : IRabbitMQConnection {
        private readonly IConnectionFactory _connectionFactory;
        private readonly int _retryCount;
        IConnection? _connection;
        bool _disposed;
        readonly object SYNC_ROOT = new();
        
        public RabbitMQConnection(IConnectionFactory connectionFactory, int retryCount = 5) {
            _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
            _retryCount = retryCount;
        }
        /// <summary>
        /// 是否已连接
        /// </summary>
        public bool IsConnected {
            get {
                return _connection != null && _connection.IsOpen && !_disposed;
            }
        }
        /// <summary>
        /// 创建Model
        /// </summary>
        /// <returns></returns>
        public IModel CreateModel() {
            if (!IsConnected) {
                throw new InvalidOperationException("No RabbitMQ connections are available to perform this action");
            }
            return _connection!.CreateModel();
        }
        /// <summary>
        /// 释放
        /// </summary>
        public void Dispose() {
            if (_disposed) {
                return;
            }
            _disposed = true;
            try {
                _connection!.Dispose();
            } catch (IOException ex) {

            }
        }
        /// <summary>
        /// 连接
        /// </summary>
        /// <returns></returns>
        public bool TryConnect() {
            // _logger.LogInformation("RabbitMQ Client is trying to connect");
            // 加锁
            lock (SYNC_ROOT) {
                // 重试策略
                var policy = Policy.Handle<SocketException>()
                    .Or<BrokerUnreachableException>()
                    .WaitAndRetry(_retryCount,
                        retryAttempt =>
                            TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => {
                                // _logger.LogWarning(ex, "RabbitMQ Client could not connect after {TimeOut}s ({ExceptionMessage})", $"{time.TotalSeconds:n1}", ex.Message);
                            }
                );
                // 执行策略
                policy.Execute(() => {
                    // 开始连接RabbitMQ
                    _connection = _connectionFactory.CreateConnection();
                });
                // 连接成功
                if (IsConnected) {
                    // 追加事件处理器，目的是为了异常重试，共3种情况
                    _connection!.ConnectionShutdown += OnConnectionShutdown;
                    _connection.CallbackException += OnCallbackException;
                    _connection.ConnectionBlocked += OnConnectionBlocked;

                    //_logger.LogInformation("RabbitMQ Client acquired a persistent connection to '{HostName}' and is subscribed to failure events", _connection.Endpoint.HostName);

                    return true;
                } else {
                    //_logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened");

                    return false;
                }
            }
        }

        /// <summary>
        /// 连接被阻断
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnConnectionBlocked(object sender, ConnectionBlockedEventArgs e) {
            if (_disposed)
                return;

            //_logger.LogWarning("A RabbitMQ connection is shutdown. Trying to re-connect...");

            TryConnect();
        }

        /// <summary>
        /// 连接出现异常
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnCallbackException(object sender, CallbackExceptionEventArgs e) {
            if (_disposed)
                return;

            // _logger.LogWarning("A RabbitMQ connection throw exception. Trying to re-connect...");

            TryConnect();
        }

        /// <summary>
        /// 连接被关闭
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="reason"></param>
        void OnConnectionShutdown(object sender, ShutdownEventArgs reason) {
            if (_disposed)
                return;

            //_logger.LogWarning("A RabbitMQ connection is on shutdown. Trying to re-connect...");

            TryConnect();
        }
    }
}
